package com.mjing.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.beanutils.converters.DateConverter;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.util.Assert;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * Description:扩展spring的BeanUtils，增加拷贝属性排除null值的功能(注：String为null不考虑)
 * Create by:Luocheng
 * Date:2014/08/27 16:05
 */
@Slf4j
public class BeanUtils extends org.springframework.beans.BeanUtils {

    public static void copyNotNullProperties(Object source, Object target, String[] ignoreProperties) throws BeansException {
        copyNotNullProperties(source, target, null, ignoreProperties);
    }

    public static void copyNotNullProperties(Object source, Object target, Class<?> editable) throws BeansException {
        copyNotNullProperties(source, target, editable, null);
    }

    public static void copyNotNullProperties(Object source, Object target) throws BeansException {
        copyNotNullProperties(source, target, null, null);
    }

    @SuppressWarnings("rawtypes")
    private static void copyNotNullProperties(Object source, Object target, Class<?> editable, String[] ignoreProperties) throws BeansException {

        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");

        Class<?> actualEditable = target.getClass();
        if (editable != null) {
            if (!editable.isInstance(target)) {
                throw new IllegalArgumentException("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]");
            }
            actualEditable = editable;
        }
        PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
        List<String> ignoreList = (ignoreProperties != null) ? Arrays.asList(ignoreProperties) : null;

        for (PropertyDescriptor targetPd : targetPds) {
            if (targetPd.getWriteMethod() != null && (ignoreProperties == null || (!ignoreList.contains(targetPd.getName())))) {
                if (targetPd.getName().equals("wxopenid")) {
                    System.out.println("aaa");
                }
                PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
                if (sourcePd != null && sourcePd.getReadMethod() != null) {
                    try {
                        Method readMethod = sourcePd.getReadMethod();
                        if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                            readMethod.setAccessible(true);
                        }
                        Object value = readMethod.invoke(source);
                        if (value != null /*|| readMethod.getReturnType().getName().equals("java.lang.String")*/) {// 这里判断以下value是否为空，当然这里也能进行一些特殊要求的处理 例如绑定时格式转换等等，如果是String类型，则不需要验证是否为空
                            boolean isEmpty = false;
                            if (value instanceof Set) {
                                Set s = (Set) value;
                                if (s == null || s.isEmpty()) {
                                    isEmpty = true;
                                }
                            } else if (value instanceof Map) {
                                Map m = (Map) value;
                                if (m == null || m.isEmpty()) {
                                    isEmpty = true;
                                }
                            } else if (value instanceof List) {
                                List l = (List) value;
                                if (l == null || l.size() < 1) {
                                    isEmpty = true;
                                }
                            } else if (value instanceof Collection) {
                                Collection c = (Collection) value;
                                if (c == null || c.size() < 1) {
                                    isEmpty = true;
                                }
                            }
                            if (!isEmpty) {
                                Method writeMethod = targetPd.getWriteMethod();
                                if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                    writeMethod.setAccessible(true);
                                }
                                writeMethod.invoke(target, value);
                            }
                        }
                    } catch (Throwable ex) {
                        throw new FatalBeanException("Could not copy properties from source to target", ex);
                    }
                }
            }
        }
    }

    public static void copyMapProperties(Object target, Object source) {
        try {
            PropertyUtils.copyProperties(target, source);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    public static Object mapToObject(Map<String, Object> map, Class<?> beanClass) throws Exception {
        if (map == null) {
            return null;
        }
        Object obj = beanClass.newInstance();
        org.apache.commons.beanutils.BeanUtils.populate(obj, map);
        return obj;
    }

    public static Map<?, ?> objectToMap(Object obj) {
        if (obj == null) {
            return null;
        }
        return new org.apache.commons.beanutils.BeanMap(obj);
    }


    /**
     * @return: void
     * @Author: wangsm 2020-03-12
     * @Param: [dest, orig]
     * @Description: 基于PropertyUtils 的数据copy
     * 【相同的属性名，数据类型不同时，会进行转换】
     * 比方法【entityToEntityBeanUtils】效率要低，但可靠性高
     */
    public static void entityToEntityProperty(Object dest, Object orig) {

        //解决BigDecimal类型转化报错，日期转换器
        BeanUtilsBean.getInstance().getConvertUtils().register(false, false, 0);
        DateConverter conver = new DateConverter(null);
        conver.setPatterns(new String[]{MDateUtils.PATTERN_YY, MDateUtils.PATTERN_YM, MDateUtils.PATTERN_YMD, MDateUtils.PATTERN_YMDHMS, MDateUtils.PATTERN_YMDHMSSS});
        //注册日期转换器
        ConvertUtils.register(conver, java.util.Date.class);
        try {
            org.apache.commons.beanutils.PropertyUtils.copyProperties(dest, orig);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("数据copy异常：调用者不必使用属性访问器方法");
        } catch (InvocationTargetException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("数据copy异常");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("数据copy异常");
        }
    }

    /**
     * @return: java.util.Map<java.lang.String, java.lang.Object>
     * @Author: wangsm 2020-04-04
     * @Param: [obj]
     * @Description: Java实体类转Map：方法一
     */
    public static Map<String, Object> entityToMap(Object obj) {

        Class<?> clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        Map<String, Object> mapData = new HashMap<String, Object>(fields.length);

        for (Field field : fields) {
            field.setAccessible(true);
            String fieldName = field.getName();
            Object object = null;
            try {
                object = field.get(obj);
            } catch (IllegalArgumentException e) {
                log.info(e.getMessage());
                throw new IllegalArgumentException("指定对象不是声明基础字段（或其子类或实现器）的类或接口的实例。");
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                log.info(e.getMessage());
                throw new IllegalArgumentException("此对象正在执行Java语言访问控制，并且基础字段不可访问。");
            } catch (NullPointerException e) {
                e.printStackTrace();
                log.info(e.getMessage());
                throw new IllegalArgumentException("指定的对象为null并且该字段是实例字段。");
            } catch (ExceptionInInitializerError e) {
                e.printStackTrace();
                log.info(e.getMessage());
                throw new IllegalArgumentException("方法【entityToMap】引发的初始化失败。");
            }
            mapData.put(fieldName, object);
        }
        return mapData;
    }

    /**
     * @return: T
     * @Author: wangsm 2020-04-04
     * @Param: [clazz, map]
     * @Description: Map转实体类：要转换的Map的key跟实体类属性名相同的数据会转过去，不相同的字段会为null
     */
    public static <T> T mapToEntity(Class<T> clazz, Map<String, Object> map) {
        T obj = null;
        try {
            BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
            // 创建 JavaBean 对象
            obj = clazz.newInstance();
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            // 给 JavaBean 对象的属性赋值
            for (int i = 0; i < propertyDescriptors.length; i++) {
                PropertyDescriptor descriptor = propertyDescriptors[i];
                String propertyName = descriptor.getName();
                if (map.containsKey(propertyName)) {
                    Object value = map.get(propertyName);
                    Object[] args = new Object[1];
                    args[0] = value;
                    try {
                        descriptor.getWriteMethod().invoke(obj, args);
                    } catch (InvocationTargetException e) {
                        log.info(e.getMessage());
                    }
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            log.info(e.getMessage());
        } catch (IntrospectionException e) {
            e.printStackTrace();
            log.info(e.getMessage());
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
            log.info(e.getMessage());
        } catch (InstantiationException e) {
            e.printStackTrace();
            log.info(e.getMessage());
        }
        return (T) obj;
    }

}
